/*
 * Copyright (c) 2012 Citrix Systems, Inc.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

// Xen Windows PV M2B Bus PDO routines

#define INITGUID 1
#include <ntddk.h>
#include <ntstrsafe.h>
#include <wdm.h>
#include <wdmguid.h>
#include <hidport.h>
#include "input.h"
#include "xmou.h"
#include "xenm2b.h"

PDEVICE_OBJECT
XenM2BPdoCreate(PXENM2B_FDO_EXTENSION pFdoExt, PCHAR pDeviceName, UCHAR SlotID)
{
    PDEVICE_OBJECT        pDevice;
    PXENM2B_PDO_EXTENSION pPdoExt;
    NTSTATUS              Status;
    size_t                NameLength;
    ANSI_STRING           AnsiName;
    ULONG                 Hash;

    Status = RtlStringCchLengthA(pDeviceName,
                                 XENM2B_PDO_NAME_LENGTH,
                                 &NameLength);
    if ((!NT_SUCCESS(Status))||(NameLength >= XENM2B_PDO_NAME_LENGTH)) {
        TraceError(("%s: Invalid device name, error: 0x%x length: 0x%x\n",
                   __FUNCTION__, Status, NameLength));
        return NULL;
    }
    if (NameLength == 0) {
        TraceError(("%s: Invalid empty device name\n", __FUNCTION__));
        return NULL;
    }

    Status = IoCreateDevice(pFdoExt->pDriver,
                            sizeof(XENM2B_PDO_EXTENSION),
                            NULL,
                            FILE_DEVICE_UNKNOWN,
                            FILE_DEVICE_SECURE_OPEN | FILE_AUTOGENERATED_DEVICE_NAME,
                            FALSE,
                            &pDevice);
    if (!NT_SUCCESS(Status)) {
        TraceError(("%s: Failed to create new PDO, error: 0x%x\n",
                   __FUNCTION__, Status));
        return NULL;
    }

    pPdoExt = (PXENM2B_PDO_EXTENSION)pDevice->DeviceExtension;
    RtlZeroMemory(pPdoExt, sizeof(XENM2B_PDO_EXTENSION));

    pPdoExt->Type = XenM2BPdo;
    pPdoExt->pDevice = pDevice;

    // Convert the name to UNICODE and store as the device TEXT.
    AnsiName.Length = (USHORT)NameLength;
    AnsiName.MaximumLength = NameLength + sizeof(CHAR);
    AnsiName.Buffer = pDeviceName;

    pPdoExt->DeviceName.Length = 0;
    pPdoExt->DeviceName.MaximumLength = XENM2B_PDO_NAME_LENGTH * sizeof(WCHAR);
    pPdoExt->DeviceName.Buffer = pPdoExt->pDeviceNameW;

    Status = RtlAnsiStringToUnicodeString(&pPdoExt->DeviceName, &AnsiName, FALSE);
    if (!NT_SUCCESS(Status)) {
        TraceError(("%s: Failed to convert ANSI device name %s to UNICODE, error: 0x%x\n",
                   __FUNCTION__, pDeviceName, Status));
        IoDeleteDevice(pDevice);
        return NULL;
    }

    // Create a unique instance ID for this device
    RtlHashUnicodeString(&pPdoExt->DeviceName, FALSE, HASH_STRING_ALGORITHM_DEFAULT, &Hash);
    Status = RtlStringCchPrintfW(&pPdoExt->pInstanceIDW[0],
                                 (XENM2B_PDO_NAME_LENGTH - 1),
                                 L"XH%8.8XS%2.2X",
                                 Hash,
                                 SlotID);
    if (!NT_SUCCESS(Status)) {
        TraceError(("%s: Failed to create Instance ID for device %s, error: 0x%x\n",
                   __FUNCTION__, pDeviceName, Status));
        IoDeleteDevice(pDevice);
        return NULL;
    }
    RtlInitUnicodeString(&pPdoExt->InstanceID, pPdoExt->pInstanceIDW);

    TraceDebug(("%s: %p -> Device: %wZ Instance: %wZ\n",
               __FUNCTION__, pDevice, &pPdoExt->DeviceName, &pPdoExt->InstanceID));

    ExInitializeFastMutex(&pPdoExt->DevicePnPState.PnPStateMutex);
    XenM2BSetPnPState(&pPdoExt->DevicePnPState, PnPStatePresent);

    pPdoExt->SystemPowerState = PowerSystemWorking;
    pPdoExt->DevicePowerState = PowerDeviceD3;

    pDevice->Flags &= ~DO_DEVICE_INITIALIZING;

    return pDevice;
}

VOID
XenM2BPdoDeleteDevice(PDEVICE_OBJECT pDeviceObject)
{
    PXENM2B_PDO_EXTENSION pPdoExt = (PXENM2B_PDO_EXTENSION)pDeviceObject->DeviceExtension;

    TraceDebug(("%s: Entry Device:%wZ\n", __FUNCTION__, &pPdoExt->DeviceName));

    XenM2BSetPnPState(&pPdoExt->DevicePnPState, PnPStateDeleted);
    XenM2BReleaseHidContext(pPdoExt->pHidCtx);
    IoDeleteDevice(pDeviceObject);

    TraceDebug(("%s: Exit\n", __FUNCTION__));
}

VOID
XenM2BPdoLink(PXENM2B_FDO_EXTENSION pFdoExt,
              PXENM2B_PDO_EXTENSION pPdoExt,
              BOOLEAN Locked)
{
    KIRQL Irql;

    if (!Locked)
        KeAcquireSpinLock(&pFdoExt->PdoListLock, &Irql);

    InsertTailList(&pFdoExt->PdoList, &pPdoExt->ListEntry);
    pPdoExt->pFdo = pFdoExt->pDevice;
    ASSERT(pFdoExt->References != 0);
    pFdoExt->References++;

    if (!Locked)
        KeReleaseSpinLock(&pFdoExt->PdoListLock, Irql);
}

VOID
XenM2BPdoUnlink(PXENM2B_PDO_EXTENSION pPdoExt,
                BOOLEAN Locked)
{
    PXENM2B_FDO_EXTENSION pFdoExt;
    PXENM2B_HID_CONTEXT   pHidCtx = pPdoExt->pHidCtx;
    PLIST_ENTRY           pEntry = &pPdoExt->ListEntry;
    KIRQL                 Irql;

    ASSERT(pPdoExt->pFdo != NULL);
    pFdoExt = (PXENM2B_FDO_EXTENSION)pPdoExt->pFdo->DeviceExtension;
    ASSERT(pFdoExt != NULL);

    if (!Locked)
        KeAcquireSpinLock(&pFdoExt->PdoListLock, &Irql);

    // If we are unlinking the active Pdo due to a device removal we need to reset the active hid context.
    //
    if (pFdoExt->pActiveHidCtx == pHidCtx) {
        pFdoExt->pActiveHidCtx = NULL;
        XenM2BReleaseHidContext(pHidCtx);
    }

    RemoveEntryList(pEntry);
    pPdoExt->pFdo = NULL;
    ASSERT(pFdoExt->References != 0);
    pFdoExt->References--;

    if (!Locked)
        KeReleaseSpinLock(&pFdoExt->PdoListLock, Irql);

    // If the removed Pdo had the final reference to the Fdo, delete the Fdo now.
    //
    if (pFdoExt->References == 0) {
        ASSERT(!Locked);
        XenM2BDeleteDevice(pFdoExt);
    }
}

static NTSTATUS
XenM2BPdoQueryDeviceRelations(PXENM2B_PDO_EXTENSION pPdoExt,
                              PIRP pIrp)
{
    PIO_STACK_LOCATION pIrpStack;
    PDEVICE_RELATIONS  pRelations;
    DEVICE_RELATION_TYPE Type;

    pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
    Type = pIrpStack->Parameters.QueryDeviceRelations.Type;
    if (Type != TargetDeviceRelation) {
        TraceDebug(("%s: Relations Type: %d\n", __FUNCTION__, Type));
        return STATUS_SUCCESS;
    }

    if (XenM2BGetPnPState(&pPdoExt->DevicePnPState) == PnPStateDeleted) {
        TraceDebug(("%s: Target deleted\n", __FUNCTION__));
        return STATUS_NO_SUCH_DEVICE;
    }

    pRelations = ExAllocatePoolWithTag(PagedPool,
                                       sizeof(DEVICE_RELATIONS),
                                       XENM2B_POOL_TAG);
    if (pRelations == NULL) {
        TraceDebug(("%s: Relations alloc failed\n", __FUNCTION__));
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    pRelations->Count = 1;
    pRelations->Objects[0] = pPdoExt->pDevice;
    ObReferenceObject(pPdoExt->pDevice);

    pIrp->IoStatus.Information = (ULONG_PTR)pRelations;
    return STATUS_SUCCESS;
}

static NTSTATUS
XenM2BPdoQueryInterface(PXENM2B_PDO_EXTENSION pPdoExt,
                        PIRP pIrp)
{
    PIO_STACK_LOCATION        pIrpStack;
    PXENM2B_CLIENT_INTERFACE  pInterface;
    const GUID               *pInterfaceType;
    USHORT                    Size;
    USHORT                    Version;
    NTSTATUS                  Status;

    TraceDebug(("%s(%wZ): ====>\n", __FUNCTION__, &pPdoExt->DeviceName));

    pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
    pInterfaceType = pIrpStack->Parameters.QueryInterface.InterfaceType;
    Size = pIrpStack->Parameters.QueryInterface.Size;
    Version = pIrpStack->Parameters.QueryInterface.Version;

    TraceDebug(("%s: %08x-%04x-%04x-%02x%02x%02x%02x%02x%02x%02x%02x\n", __FUNCTION__,
               pInterfaceType->Data1,
               pInterfaceType->Data2,
               pInterfaceType->Data3,
               pInterfaceType->Data4[0],
               pInterfaceType->Data4[1],
               pInterfaceType->Data4[2],
               pInterfaceType->Data4[3],
               pInterfaceType->Data4[4],
               pInterfaceType->Data4[5],
               pInterfaceType->Data4[6],
               pInterfaceType->Data4[7]));

    if (!RtlEqualMemory(pInterfaceType, &XENM2B_CLIENT_INTERFACE_GUID, sizeof(GUID)))
        return STATUS_SUCCESS;

    if (Version != XENM2B_CLIENT_INTERFACE_VERSION)
        return STATUS_SUCCESS;

    if (Size < sizeof(XENM2B_CLIENT_INTERFACE))
        return STATUS_BUFFER_TOO_SMALL;

    // Get the input client interface - PXENHID_OPERATIONS
    pInterface = (PXENM2B_CLIENT_INTERFACE)pIrpStack->Parameters.QueryInterface.Interface;

    ASSERT(pInterface->Header.Size == sizeof(XENM2B_CLIENT_INTERFACE));
    ASSERT(pInterface->Header.Version == XENM2B_CLIENT_INTERFACE_VERSION);

    TraceDebug(("%s: CLIENT Version: %04x\n", __FUNCTION__, pInterface->Header.Version));

    pPdoExt->Interface.pXenHidContext = pInterface->Header.Context;
    pPdoExt->Interface.pXenHidOperations = pInterface->Operations.pXenHid;    

    // Set the output server interface - PXENM2B_OPERATIONS
    pInterface->Header.Version = XENM2B_SERVER_INTERFACE_VERSION;

    TraceDebug(("%s: SERVER Version: %04x\n", __FUNCTION__, pInterface->Header.Version));

    pInterface->Operations.pXenM2B = XenM2BGetInterfaceOperations();
    pInterface->Header.Context = &pPdoExt->Interface; // Interface block is used as the context.

    pInterface->Header.InterfaceReference = XenM2BInterfaceReference;
    pInterface->Header.InterfaceDereference = XenM2BInterfaceDereference;    

    // Finally call this once to set the state to referenced and initialize the spin lock.
    XenM2BInterfaceReference(&pPdoExt->Interface);

    pIrp->IoStatus.Information = 0;

    TraceDebug(("%s: <====\n", __FUNCTION__));

    return STATUS_SUCCESS;
}

static NTSTATUS
XenM2BPdoQueryCapabilities(PXENM2B_PDO_EXTENSION pPdoExt,
                           PIRP pIrp)
{
    PIO_STACK_LOCATION   pIrpStack;
    PDEVICE_CAPABILITIES pCapabilities;
    SYSTEM_POWER_STATE   SystemPowerState;

    UNREFERENCED_PARAMETER(pPdoExt);

    pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
    pCapabilities = pIrpStack->Parameters.DeviceCapabilities.Capabilities;

    if (pCapabilities->Version != 1)
        return STATUS_INVALID_PARAMETER;

    pCapabilities->DeviceD1 = 0;
    pCapabilities->DeviceD2 = 0;
    pCapabilities->LockSupported = 0;
    pCapabilities->EjectSupported = 1;
    pCapabilities->Removable = 1;
    pCapabilities->DockDevice = 0;
    pCapabilities->UniqueID = 1;
    pCapabilities->SilentInstall = 0;
    pCapabilities->RawDeviceOK = 0;
    pCapabilities->SurpriseRemovalOK = 0;
    pCapabilities->HardwareDisabled = 0;
    pCapabilities->NoDisplayInUI = 0;

    pCapabilities->Address = 0xffffffff;
    pCapabilities->UINumber = 0xffffffff;

    for (SystemPowerState = 0; SystemPowerState < PowerSystemMaximum; SystemPowerState++) {
        switch (SystemPowerState) {
        case PowerSystemUnspecified:
            break;
        case PowerSystemWorking:
            pCapabilities->DeviceState[SystemPowerState] = PowerDeviceD0;
            break;
        default:
            pCapabilities->DeviceState[SystemPowerState] = PowerDeviceD3;
            break;
        }
    }

    pCapabilities->SystemWake = PowerSystemUnspecified;
    pCapabilities->DeviceWake = PowerDeviceUnspecified;
    pCapabilities->D1Latency = 0;
    pCapabilities->D2Latency = 0;
    pCapabilities->D3Latency = 0;

    return STATUS_SUCCESS;
}

static NTSTATUS
XenM2BPdoQueryDeviceText(PXENM2B_PDO_EXTENSION pPdoExt,
                         PIRP pIrp)
{
    PIO_STACK_LOCATION pIrpStack;
    PVOID              pBuffer;
    UNICODE_STRING     Text;
    NTSTATUS           Status;

    pIrpStack = IoGetCurrentIrpStackLocation(pIrp);

    switch (pIrpStack->Parameters.QueryDeviceText.DeviceTextType) {
    case DeviceTextDescription:
        TraceDebug(("%s: DeviceTextDescription\n", __FUNCTION__));
        break;

    case DeviceTextLocationInformation:
        TraceDebug(("%s: DeviceTextLocationInformation\n", __FUNCTION__));
        break;

    default:
        pIrp->IoStatus.Information = 0;
        return STATUS_NOT_SUPPORTED;
    }

    pBuffer = ExAllocatePoolWithTag(PagedPool,
                                    XENM2B_MAX_TEXT_LENGTH,
                                    XENM2B_POOL_TAG);
    if (pBuffer == NULL)
        return STATUS_INSUFFICIENT_RESOURCES;
    RtlZeroMemory(pBuffer, XENM2B_MAX_TEXT_LENGTH);

    Text.Buffer = pBuffer;
    Text.MaximumLength = XENM2B_MAX_TEXT_LENGTH;
    Text.Length = 0;

    switch (pIrpStack->Parameters.QueryDeviceText.DeviceTextType) {
    case DeviceTextDescription:
        RtlAppendUnicodeStringToString(&Text, &pPdoExt->DeviceName);        
        break;

    case DeviceTextLocationInformation:
        RtlAppendUnicodeToString(&Text, L"Xen PV HID Device on XenM2B Bus");
        break;

    default:
        ASSERT(FALSE);
        break;
    }

    TraceDebug(("%s: %wZ\n", __FUNCTION__, &Text));

    pIrp->IoStatus.Information = (ULONG_PTR)pBuffer;
    return STATUS_SUCCESS;
}

static NTSTATUS
XenM2BPdoQueryId(PXENM2B_PDO_EXTENSION pPdoExt,
                 PIRP pIrp)
{
    PIO_STACK_LOCATION pIrpStack;
    PWCHAR             pBuffer;
    UNICODE_STRING     Id;
    ULONG              Type;
    size_t             Length;
    NTSTATUS           Status;

    pIrpStack = IoGetCurrentIrpStackLocation(pIrp);

    switch (pIrpStack->Parameters.QueryId.IdType) {
    case BusQueryInstanceID:
        TraceDebug(("%s: BusQueryInstanceID\n", __FUNCTION__));
        break;

    case BusQueryDeviceID:
        TraceDebug(("%s: BusQueryDeviceID\n", __FUNCTION__));
        break;

    case BusQueryHardwareIDs:
        TraceDebug(("%s: BusQueryHardwareIDs\n", __FUNCTION__));
        break;

    default:
        pIrp->IoStatus.Information = 0;
        return STATUS_NOT_SUPPORTED;
    }

    pBuffer = ExAllocatePoolWithTag(PagedPool,
                                    XENM2B_MAX_DEVID_LENGTH,
                                    XENM2B_POOL_TAG);
    if (pBuffer == NULL)
        return STATUS_INSUFFICIENT_RESOURCES;
    RtlZeroMemory(pBuffer, XENM2B_MAX_DEVID_LENGTH);

    Id.Buffer = pBuffer;
    Id.MaximumLength = XENM2B_MAX_DEVID_LENGTH;
    Id.Length = 0;

    switch (pIrpStack->Parameters.QueryId.IdType) {
    case BusQueryInstanceID:
        Type = REG_SZ;
        RtlAppendUnicodeStringToString(&Id, &pPdoExt->InstanceID);
        break;

    case BusQueryDeviceID:
        Type = REG_SZ;
        RtlAppendUnicodeToString(&Id, L"XENM2B\\DEVICE");
        break;

    case BusQueryHardwareIDs:
        Type = REG_MULTI_SZ;
        RtlAppendUnicodeToString(&Id, L"XENM2B\\DEVICE");

        Id.Buffer += Id.Length/2;
        Id.Buffer++; // past terminator
        Id.MaximumLength = Id.MaximumLength - Id.Length;
        Id.MaximumLength -= sizeof(WCHAR);
        Id.Length = 0;

        RtlAppendUnicodeToString(&Id, L"XENDEVICE");
        break;

    default:
        Type = REG_NONE;
        ASSERT(FALSE);
        break;
    }

    Id.Buffer = pBuffer;
    Id.MaximumLength = XENM2B_MAX_DEVID_LENGTH;
    Id.Length = 0;

    switch (Type) {
    case REG_SZ:
        TraceDebug(("%s: %ws\n", __FUNCTION__, Id.Buffer));
        break;

    case REG_MULTI_SZ:
        do {
            TraceDebug(("%s: - %ws\n", __FUNCTION__, Id.Buffer));
            Id.Buffer += wcslen(Id.Buffer);
            Id.Buffer++;
        } while (*Id.Buffer != L'\0');
        break;

    default:
        ASSERT(FALSE);
        break;
    }

    pIrp->IoStatus.Information = (ULONG_PTR)pBuffer;
    return STATUS_SUCCESS;
}

static NTSTATUS
XenM2BPdoQueryBusInformation(PXENM2B_PDO_EXTENSION pPdoExt,
                             PIRP pIrp)
{
    PPNP_BUS_INFORMATION pInfo;

    UNREFERENCED_PARAMETER(pPdoExt);

    pInfo = ExAllocatePoolWithTag(PagedPool,
                                  sizeof(PNP_BUS_INFORMATION),
                                  XENM2B_POOL_TAG);
    if (pInfo == NULL)
        return STATUS_INSUFFICIENT_RESOURCES;

    pInfo->BusTypeGuid = GUID_BUS_TYPE_INTERNAL;
    pInfo->LegacyBusType = Internal;
    pInfo->BusNumber = 0;

    pIrp->IoStatus.Information = (ULONG_PTR)pInfo;
    return STATUS_SUCCESS;
}

static NTSTATUS
XenM2BPdoDeviceUsageCompletion(PDEVICE_OBJECT pDeviceObject,
                               PIRP pSubIrp,
                               PVOID pContext)
{
    PIO_STACK_LOCATION pSubIrpStack;
    PIRP               pIrp;

    UNREFERENCED_PARAMETER(pContext);

    // Drop the reference we hold on the FDO stack
    ObDereferenceObject(pDeviceObject);

    pSubIrpStack = IoGetCurrentIrpStackLocation(pSubIrp);
    pIrp = (PIRP)pSubIrpStack->Parameters.Others.Argument1;

    if (pSubIrp->IoStatus.Status != STATUS_NOT_SUPPORTED)
        pIrp->IoStatus = pSubIrp->IoStatus;

    IoFreeIrp(pSubIrp);
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);

    return STATUS_MORE_PROCESSING_REQUIRED;
}

static NTSTATUS
XenM2BPdoDeviceUsageNotification(PXENM2B_PDO_EXTENSION pPdoExt,
                                 PIRP pIrp)
{
    PDEVICE_OBJECT        pDeviceObject;
    PIO_STACK_LOCATION    pIrpStack;
    PIRP                  pSubIrp;
    PIO_STACK_LOCATION    pSubIrpStack;
    NTSTATUS              Status;

    pIrpStack = IoGetCurrentIrpStackLocation(pIrp);

    // Find the top of the FDO stack and hold a reference
    pDeviceObject = IoGetAttachedDeviceReference(pPdoExt->pFdo);

    // Get a new IRP for the FDO stack and reserve an extra stack location
    // for our use here.
    pSubIrp = IoAllocateIrp(pDeviceObject->StackSize + 1, FALSE);
    if (pSubIrp == NULL)
        return STATUS_INSUFFICIENT_RESOURCES;

    // Fill in the first stack location with our context information
    // which we'll need to complete the original IRP.
    pSubIrpStack = IoGetNextIrpStackLocation(pSubIrp);
    pSubIrpStack->DeviceObject = pDeviceObject;
    pSubIrpStack->Parameters.Others.Argument1 = (PVOID)pIrp;

    // Advance the stack location, copy in the information from the
    // original IRP and set up our completion routine.
    IoSetNextIrpStackLocation(pSubIrp);
    pSubIrpStack = IoGetNextIrpStackLocation(pSubIrp);

    RtlCopyMemory(pSubIrpStack,
                  pIrpStack,
                  FIELD_OFFSET(IO_STACK_LOCATION, CompletionRoutine));
    pSubIrpStack->Control = 0;

    IoSetCompletionRoutine(pSubIrp,
                           XenM2BPdoDeviceUsageCompletion,
                           NULL,
                           TRUE,
                           TRUE,
                           TRUE);

    // Default completion status
    pSubIrp->IoStatus.Status = STATUS_NOT_SUPPORTED;

    IoMarkIrpPending(pIrp);
    IoCallDriver(pDeviceObject, pSubIrp);

    return STATUS_PENDING;
}

NTSTATUS
XenM2BPdoPnP(PDEVICE_OBJECT pDeviceObject, PIRP pIrp)
{
    PXENM2B_PDO_EXTENSION pPdoExt = (PXENM2B_PDO_EXTENSION)pDeviceObject->DeviceExtension;
    PIO_STACK_LOCATION    pIrpStack;
    NTSTATUS              Status = STATUS_SUCCESS;

    pIrpStack = IoGetCurrentIrpStackLocation(pIrp);

    switch (pIrpStack->MinorFunction) {
    case IRP_MN_START_DEVICE:
        TraceDebug(("%s: IRP_MN_START_DEVICE\n", __FUNCTION__));

        pPdoExt->DevicePowerState = PowerDeviceD0;
        XenM2BSetPnPState(&pPdoExt->DevicePnPState, PnPStateStarted);
        break;

    case IRP_MN_STOP_DEVICE:
        TraceDebug(("%s: IRP_MN_STOP_DEVICE\n", __FUNCTION__));

        XenM2BSetPnPState(&pPdoExt->DevicePnPState, PnPStateStopped);
        break;

    case IRP_MN_SURPRISE_REMOVAL:
        TraceDebug(("%s: IRP_MN_SURPRISE_REMOVAL\n", __FUNCTION__));

        XenM2BSetPnPState(&pPdoExt->DevicePnPState, PnPStateSurpriseRemoval);
        // TODO test removal vs. surprise removal
        break;

    case IRP_MN_REMOVE_DEVICE:
        TraceDebug(("%s: IRP_MN_REMOVE_DEVICE\n", __FUNCTION__));

        // Already gone.
        if (XenM2BGetPnPState(&pPdoExt->DevicePnPState) == PnPStateDeleted) {
            TraceDebug(("%s: Device deleted\n", __FUNCTION__));
            break;
        }

        if (pPdoExt->Missing) {
            // DEV_RESET will set the Missing flag before causing a re-enumeration in the FDO.
            // The FDO will report this PDO as not present in the relations query and it will
            // be removed here.
            TraceDebug(("%s: Delete:%wZ\n", __FUNCTION__, &pPdoExt->DeviceName));
            XenM2BPdoUnlink(pPdoExt, FALSE);
            XenM2BPdoDeleteDevice(pPdoExt->pDevice);
        }
        else {
            TraceDebug(("%s: Enumerated:%wZ\n", __FUNCTION__, &pPdoExt->DeviceName));
            XenM2BSetPnPState(&pPdoExt->DevicePnPState, PnPStateEnumerated);
        }
        break;

    case IRP_MN_EJECT:
        TraceDebug(("%s: IRP_MN_EJECT\n", __FUNCTION__));

        // Already gone.
        if (XenM2BGetPnPState(&pPdoExt->DevicePnPState) == PnPStateDeleted) {
            TraceDebug(("%s: Device deleted\n", __FUNCTION__));
            break;
        }

        XenM2BPdoUnlink(pPdoExt, FALSE);
        XenM2BPdoDeleteDevice(pPdoExt->pDevice);
		break;

    case IRP_MN_QUERY_REMOVE_DEVICE:
        TraceDebug(("%s: IRP_MN_QUERY_REMOVE_DEVICE\n", __FUNCTION__));

        XenM2BSetPnPState(&pPdoExt->DevicePnPState, PnPStateRemovePending);
        break;

    case IRP_MN_QUERY_STOP_DEVICE:
        TraceDebug(("%s: IRP_MN_QUERY_STOP_DEVICE\n", __FUNCTION__));

        XenM2BSetPnPState(&pPdoExt->DevicePnPState, PnPStateStopPending);
        break;

    case IRP_MN_CANCEL_REMOVE_DEVICE:
        TraceDebug(("%s: IRP_MN_CANCEL_REMOVE_DEVICE\n", __FUNCTION__));

        (VOID)XenM2BRestorePnPState(&pPdoExt->DevicePnPState, PnPStateRemovePending);
        break;

    case IRP_MN_CANCEL_STOP_DEVICE:
        TraceDebug(("%s: IRP_MN_CANCEL_STOP_DEVICE\n", __FUNCTION__));

        (VOID)XenM2BRestorePnPState(&pPdoExt->DevicePnPState, PnPStateStopPending);
        break;

    case IRP_MN_QUERY_DEVICE_RELATIONS:
        TraceDebug(("%s: IRP_MN_QUERY_DEVICE_RELATIONS\n", __FUNCTION__));

        Status = XenM2BPdoQueryDeviceRelations(pPdoExt, pIrp);
        break;

    case IRP_MN_QUERY_INTERFACE:
        Status = XenM2BPdoQueryInterface(pPdoExt, pIrp);
        break;

    case IRP_MN_QUERY_CAPABILITIES:
        Status = XenM2BPdoQueryCapabilities(pPdoExt, pIrp);
        break;

    case IRP_MN_QUERY_DEVICE_TEXT:
        Status = XenM2BPdoQueryDeviceText(pPdoExt, pIrp);
        break;

    case IRP_MN_QUERY_ID:
        Status = XenM2BPdoQueryId(pPdoExt, pIrp);
        break;

    case IRP_MN_QUERY_BUS_INFORMATION:
        Status = XenM2BPdoQueryBusInformation(pPdoExt, pIrp);
        break;

    case IRP_MN_DEVICE_USAGE_NOTIFICATION:
        Status = XenM2BPdoDeviceUsageNotification(pPdoExt, pIrp);
        if (Status == STATUS_PENDING)
            return Status;
        break;

    case IRP_MN_READ_CONFIG:
    case IRP_MN_WRITE_CONFIG:
        Status = STATUS_NOT_SUPPORTED;
        break;

    case IRP_MN_FILTER_RESOURCE_REQUIREMENTS:
    case IRP_MN_QUERY_RESOURCES:
    case IRP_MN_QUERY_RESOURCE_REQUIREMENTS:
    case IRP_MN_QUERY_PNP_DEVICE_STATE:
    case IRP_MN_SET_LOCK:
    default:
        Status = pIrp->IoStatus.Status;
        break;
    }

    pIrp->IoStatus.Status = Status;
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    return Status;
}

NTSTATUS
XenM2BPdoPower(PDEVICE_OBJECT pDeviceObject, PIRP pIrp)
{
    PXENM2B_PDO_EXTENSION pPdoExt = (PXENM2B_PDO_EXTENSION)pDeviceObject->DeviceExtension;
    PIO_STACK_LOCATION    pIrpStack;
    POWER_STATE           PowerState;
    POWER_STATE_TYPE      PowerType;
    NTSTATUS              Status = STATUS_SUCCESS;

    pIrpStack = IoGetCurrentIrpStackLocation(pIrp);

    PowerType = pIrpStack->Parameters.Power.Type;
    PowerState = pIrpStack->Parameters.Power.State;

    switch (pIrpStack->MinorFunction) {
    case IRP_MN_SET_POWER:
        TraceDebug(("%s: IRP_MN_SET_POWER\n", __FUNCTION__));

        if (PowerType == DevicePowerState) {
            TraceDebug(("%s: IRP_MN_SET_POWER DevicePowerState:%x\n", __FUNCTION__, PowerState.DeviceState));

            PoSetPowerState(pPdoExt->pDevice, DevicePowerState, PowerState);
            pPdoExt->DevicePowerState = PowerState.DeviceState;
        }
        else if (PowerType == SystemPowerState) {
            TraceDebug(("%s: IRP_MN_SET_POWER SystemPowerState:%x\n", __FUNCTION__, PowerState.SystemState));

            pPdoExt->SystemPowerState = PowerState.SystemState;
        }

        PoStartNextPowerIrp(pIrp);
        pIrp->IoStatus.Status = Status;
        IoCompleteRequest(pIrp, IO_NO_INCREMENT);
        break;

    case IRP_MN_QUERY_POWER:
        TraceDebug(("%s: IRP_MN_QUERY_POWER\n", __FUNCTION__));
        PoStartNextPowerIrp(pIrp);
        pIrp->IoStatus.Status = Status;
        IoCompleteRequest(pIrp, IO_NO_INCREMENT);
        break;

    case IRP_MN_WAIT_WAKE:
    case IRP_MN_POWER_SEQUENCE:
    default:
        PoStartNextPowerIrp(pIrp);
        Status = pIrp->IoStatus.Status;
        IoCompleteRequest(pIrp, IO_NO_INCREMENT);
        break;
    }

    return Status;
}

